// Copyright (C) Calum Grant 2008

namespace dynamic
{
	namespace internal
	{
		/// Implements a C++ container to a variable.
		/** \ingroup  impl */
		template<typename Impl>
		class wrap_cpp_container : public shared_var_impl
		{
			int m_iterator_count;

		protected:
			typedef typename Impl::container container;

			typedef typename container::iterator iterator;

			typedef typename container::reverse_iterator reverse_iterator;

			container m_data;

			void invalidate_iterators()
			{
				++m_iterator_count;
			}

		public:
			wrap_cpp_container() 
			{ 
			}

			wrap_cpp_container(const wrap_cpp_container & other) :
				m_data(other.m_data)
			{
			}

			/// Wraps the C++ container iterator.
			template<typename Iterator>
			class wrap_iterator : public shared_var_impl
			{
			public:
				Iterator m_it, m_begin, m_end;

				wrap_cpp_container * m_container;

				int m_iterator_count;
			public:
				wrap_iterator(Iterator i, 
					Iterator begin, 
					Iterator end, 
					wrap_cpp_container * c, 
					int count
					) :
					m_it(i), 
					m_begin(begin), 
					m_end(end), 
					m_container(c), 
					m_iterator_count(count)
				{
				}

				void mark_children(gc::garbage_collector & gc)
				{
					gc.mark_reachable(m_container);
				}

				var key()
				{
					if(m_container->m_iterator_count != m_iterator_count)
						throw expired_iterator();

					return Impl::key(*m_it);
				}

				var value()
				{
					if(m_container->m_iterator_count != m_iterator_count)
						throw expired_iterator();

					return Impl::value(*m_it);
				}

				var deref()
				{
					if(m_container->m_iterator_count != m_iterator_count)
						throw expired_iterator();

					if(m_it == m_end) throw expired_iterator();
					return Impl::deref(*m_it);
				}

				void op_inc()
				{
					if(m_container->m_iterator_count != m_iterator_count)
						throw expired_iterator();

					if( m_it != m_end )
						++m_it;
				}

				bool as_bool() 
				{ 
					return m_it != m_end; 
				}

				void op_dec()
				{
					if(m_container->m_iterator_count != m_iterator_count)
						throw expired_iterator();

					if( m_it != m_begin )
						--m_it;
				}

				var_cmp_result compare(const var & other)
				{
					wrap_iterator * other_iterator = dynamic_cast<wrap_iterator*>(other.impl().shared_var());

					return other_iterator && m_it == other_iterator->m_it ?
						cmp_equal : cmp_not_equal;
				}

				var clone()
				{
					return new wrap_iterator(*this);
				}

				std::string class_name() { return Impl::iterator_class_name(); }
			};


			void erase(const var & i)
			{
				// NB Could also erase a reverse_iterator (not implemented)
				wrap_iterator<iterator> * wi = dynamic_cast<wrap_iterator<iterator>*>(i.impl().shared_var());

				if(wi)
				{
					if(wi->m_iterator_count == m_iterator_count)
					{
						if( wi->m_it != wi->m_end )
						{
							invalidate_iterators();
							m_data.erase(wi->m_it);
						}
					}
					else
					{
						throw expired_iterator();
					}
				}
			}

			var begin()
			{
				return new wrap_iterator<iterator>(
					m_data.begin(), 
					m_data.begin(), 
					m_data.end(), 
					this, 
					m_iterator_count);
			}

			var enumerator()
			{
				return begin();
			}

			var clone()
			{
				return new wrap_cpp_container(*this);
			}

			std::string class_name() 
			{ 
				return Impl::class_name();
			}

			var end()
			{
				return new wrap_iterator<iterator>(
					m_data.end(), 
					m_data.begin(), 
					m_data.end(), 
					this, 
					m_iterator_count);
			}

			var rbegin()
			{
				return new wrap_iterator<reverse_iterator>(
					m_data.rbegin(), 
					m_data.rbegin(), 
					m_data.rend(), 
					this, 
					m_iterator_count);
			}

			var rend()
			{
				return new wrap_iterator<reverse_iterator>(
					m_data.rend(), 
					m_data.rbegin(), 
					m_data.rend(), 
					this, 
					m_iterator_count);
			}

			var reverse_enumerator()
			{
				return rbegin();
			}

			bool empty()
			{
				return m_data.empty();
			}

			bool as_bool()
			{
				return !empty();
			}

			void clear()
			{
				invalidate_iterators();
				m_data.clear();
			}

			var values()
			{
				return this;
			}

			int size()
			{
				return int(m_data.size());
			}

			void mark_children(gc::garbage_collector & gc)
			{
				for(iterator i=m_data.begin(); i!=m_data.end(); ++i)
					Impl::mark_reachable(*i, gc);
			}

			var_cmp_index comparison_index()
			{
				return Impl::comparison_index;
			}
		};


		/// Implements an associative container.
		/** \ingroup  impl */
		template<typename Impl>
		class wrap_assoc_container : public wrap_cpp_container<Impl>
		{
		public:
			typedef typename wrap_cpp_container<Impl>::iterator iterator;

			void set_member(const var & k, const var & v)
			{
				this->invalidate_iterators();	// Is this necessary?
				(this->m_data)[Impl::key(k)] = v;
			}

			var call(const var & k, const var & v)
			{
				this->invalidate_iterators();	// Is this necessary?
				(this->m_data)[Impl::key(k)] = v;
				return this;
			}

			int max_args() 
			{
				return 2; 
			}

			var get_member(const var & k)
			{
				iterator i=this->m_data.find( Impl::key(k) );
				return i == this->m_data.end() ? var() : Impl::value(*i);
			}

			void erase(const var & k)
			{
				this->m_data.erase( Impl::key(k) );
				this->invalidate_iterators();
			}

			bool contains(const var & k)
			{
				iterator i=this->m_data.find( Impl::key(k) );
				return i != this->m_data.end();
			}

			var values() 
			{ 
				/// \todo For now, we populate an array with the answer.
				// In future, it could be better to implement a despoke iterator

				var v = array();
				v.impl().reserve(this->m_data.size());
				for(typename Impl::container::iterator i=this->m_data.begin();
					i!=this->m_data.end(); ++i)
						v.push_back(i->second.get());

				return v;
			}

			void short_output(ostream & ss)
			{
				ss << "{ ... }";
			}

			void output(ostream & ss)
			{
				ss << '{';

				bool first=true;
				for(iterator i=this->m_data.begin(); i!=this->m_data.end(); ++i)
				{
					if(first) first=false;
					else ss << ',';
					Impl::key(*i).impl().short_output(ss);
					ss << ':';
					Impl::value(*i).impl().short_output(ss);
				}

				ss << '}';
			}

			void short_output(wostream & ss)
			{
				ss << L"{ ... }";
			}

			void output(wostream & ss)
			{
				ss << L'{';

				bool first=true;
				for(iterator i=this->m_data.begin(); i!=this->m_data.end(); ++i)
				{
					if(first) first=false;
					else ss << L',';
					Impl::key(*i).impl().short_output(ss);
					ss << L':';
					Impl::value(*i).impl().short_output(ss);
				}

				ss << L'}';
			}

			var clone()
			{
				return new wrap_assoc_container(*this);
			}

			var keys() 
			{ 
				return this; 
			}

			void pickle(pickler & p)
			{
				p.write_object_type(Impl::comparison_index);
				p.write_int(int(this->m_data.size()));
				for(iterator i=this->m_data.begin(); i!=this->m_data.end(); ++i)
				{
					p.write_object(Impl::key(*i));
					p.write_object(Impl::value(*i));
				}
			}

			void unpickle(unpickler & p)
			{
				int size = p.read_int();
				for(int i=0; i<size; ++i)
				{
					var k = p.read_object();
					var v = p.read_object();
					this->m_data.insert( std::make_pair( Impl::key(k), v) );
				}
			}

			void mixin(const var & other)
			{
				foreach(i, other)
					set_member(i, other[i]);
			}

			void assign_add(const var & other)
			{
				mixin(other);
			}

			var op_add(const var & other)
			{
				return clone()+=other;
			}
		};


		/// Implements an array-style variable.
		/** \ingroup  impl */
		template<typename Impl>
		class wrap_array_container : public wrap_cpp_container<Impl>
		{
		public:
			typedef typename wrap_cpp_container<Impl>::iterator iterator;
			
			void push_back(const var & v)
			{
				this->invalidate_iterators();
				this->m_data.push_back(Impl::to_value(v));
			}

			var pop_back()
			{
				if(this->m_data.empty())
					return var();
				else
				{
					var result = Impl::back(this->m_data);
					Impl::pop_back(this->m_data);
					this->invalidate_iterators();
					return result;
				}
			}

			var call(const var & a0)
			{
				this->invalidate_iterators();
				this->m_data.push_back(Impl::to_value(a0));
				return this;
			}

			int max_args() 
			{ 
				return 1; 
			}

			var front()
			{
				return this->m_data.empty() ? var() : Impl::front(this->m_data);
			}

			var back()
			{
				return this->m_data.empty() ? var() : Impl::back(this->m_data);
			}

			var clone()
			{
				return new wrap_array_container(*this);
			}

			var keys()
			{
				return this->empty() ? var() : dynamic::range(0, this->size()-1);
			}

			void short_output(ostream & ss)
			{
				ss << "[ ... ]";
			}

			void output(ostream & ss)
			{
				ss << '[';

				bool first=true;
				for(iterator i=this->m_data.begin(); i!=this->m_data.end(); ++i)
				{
					if(first) first=false;
					else ss << ',';
					Impl::value(*i).impl().short_output(ss);
				}

				ss << ']';
			}

			void short_output(wostream & ss)
			{
				ss << L"[ ... ]";
			}

			void output(wostream & ss)
			{
				ss << L'[';

				bool first=true;
				for(iterator i=this->m_data.begin(); i!=this->m_data.end(); ++i)
				{
					if(first) first=false;
					else ss << L',';
					Impl::value(*i).impl().short_output(ss);
				}

				ss << L']';
			}

			void pickle(pickler & p)
			{
				p.write_object_type(Impl::comparison_index);
				p.write_int(int(this->m_data.size()));
				for(iterator i=this->m_data.begin(); i!=this->m_data.end(); ++i)
				{
					Impl::pickle(p, *i);
				}
			}

			void unpickle(unpickler & p)
			{
				int size = p.read_int();
				for(int i=0; i<size; ++i)
				{
					this->m_data.push_back( Impl::unpickle(p) );
				}
			}

			var op_add(const var & v)
			{
				return clone()+=v;
			}

			void assign_add(const var & v)
			{
				foreach(i, v) this->push_back(i);
			}

			var op_mul(const var & v)
			{
				return clone()*=v;
			}

			void assign_mul(const var & v)
			{
				this->invalidate_iterators();
				int n=v.as_int();
				int s=this->m_data.size();
				if(n == 0) this->m_data.clear();
				this->reserve(s*n);
				for(int i=1; i<n; ++i)
				{
					iterator it = this->m_data.begin();
					for(int j=0; j<s; ++j)
					{					
						this->m_data.push_back(*it++);
					}
				}
			}
		};


		/// Implements a vector variable.
		/** \ingroup  impl */
		template<typename Impl>
		class wrap_vector_container : public wrap_array_container<Impl>
		{
			typedef typename Impl::container container;

			int get_index(int i2, container & data)
			{
				return i2>=0 ? i2 : data.size()+i2;	// NB Can still be negative
			}

		public:
			
			void set_member(const var & k, const var & v)
			{
				int index = get_index(k.as_int(), this->m_data);
				if(index < 0) throw not_found(k);

				if(unsigned(index) >= this->m_data.size())
				{
					this->invalidate_iterators();
					this->m_data.resize(index+1, Impl::pad_value());
				}
				(this->m_data)[index] = Impl::to_value(v);
			}

			void set_member(int k, const var & v)
			{
				int index = get_index(k, this->m_data);
				if(index < 0) throw not_found(k);

				if(unsigned(index) >= this->m_data.size())
				{
					this->invalidate_iterators();
					this->m_data.resize(index+1, Impl::pad_value());
				}
				(this->m_data)[index] = Impl::to_value(v);
			}

			bool contains(const var & k)
			{
				int index = get_index(k.as_int(), this->m_data);
				return index>=0 && std::size_t(index)<this->m_data.size();
			}

			void resize(std::size_t new_size)
			{
				this->invalidate_iterators();
				this->m_data.resize(new_size, Impl::pad_value());
			}		

			var get_member(int k)
			{
				int index = get_index(k, this->m_data);
				if(index<0 || unsigned(index) >= this->m_data.size()) return Impl::pad_value();
				return Impl::deref((this->m_data)[index]);
			}

			var get_member(const var & k)
			{
				int index = get_index(k.as_int(), this->m_data);
				if(index<0 || unsigned(index) >= this->m_data.size()) return Impl::pad_value();
				return Impl::deref((this->m_data)[index]);
			}

			void erase(const var & k)
			{
				std::size_t my_size = this->m_data.size();
				std::pair<std::size_t,std::size_t> r;
				k.impl().get_range(my_size, r);
				if(r.first > my_size) r.first = my_size;
				if(r.second > my_size) r.second = my_size;

				if(r.first != r.second)	// Stupid bug in cygwin/gcc 3.4.4
				{
					this->invalidate_iterators();
					this->m_data.erase(this->m_data.begin()+r.first, this->m_data.begin()+r.second);
				}
			}

			void insert(const var & k, const var & value)
			{
				int index = get_index(k.as_int(), this->m_data);

				this->invalidate_iterators();

				if(index < 0)
				{
					this->m_data.insert(this->m_data.begin(), -index, Impl::pad_value());
					index=0;
				}
				else if(unsigned(index) >= this->m_data.size())
				{
					this->m_data.resize(index, Impl::pad_value());
				}

				this->m_data.insert(this->m_data.begin()+index, value.size(), Impl::pad_value());

				// Snag: we just changed the size, so the following loop may crash!
				// This is why the odd invalidate_iterators in the loop.

				foreach( i, value )
				{
					this->invalidate_iterators();
					this->m_data[index++] = Impl::to_value(i);
				}
			}

			var clone()
			{
				return new wrap_vector_container(*this);
			}

			void reserve(std::size_t s)
			{
				this->invalidate_iterators();
				this->m_data.reserve(s);
			}

			std::size_t capacity()
			{
				return this->m_data.capacity();
			}

			void assign_mul(const var & v)
			{
				this->invalidate_iterators();
				int n=v.as_int();
				int s=this->m_data.size();
				if(n == 0) this->m_data.clear();
				reserve(s*n);
				for(int i=1; i<n; ++i)
					for(int j=0; j<s; ++j)
						this->m_data.push_back(this->m_data[j]);
			}
		};


		/// Implements a list variable.
		/** \ingroup  impl */
		template<typename Impl>
		class wrap_list_container : public wrap_array_container<Impl>
		{
		public:	
			void push_front(const var & v)
			{
				this->m_data.push_front(v);
				this->invalidate_iterators();
			}

			var pop_front()
			{
				if(this->m_data.empty())
					return var();
				else
				{
					var result = this->m_data.front().get();
					this->m_data.pop_front();
					this->invalidate_iterators();
					return result;
				}
			}

			var clone()
			{
				return new wrap_list_container(*this);
			}
		};
	}
}
